from abc import abstractmethod, ABCMeta
from collections import deque
from itertools import chain
from typing import  Optional
from feasytools import RangeList, TimeFunc, makeFunc, OverrideFunc
from .evdict import EVDict

IntPairList=list[tuple[int,int]]
PriceList=tuple[list[int],list[float]]
    
class CS(metaclass = ABCMeta):
    '''
    充电站 (Charging Station)
    '''
    @abstractmethod
    def __init__(self,name:str,slots:int,node:str,price_buy:PriceList,price_sell:Optional[PriceList],offline:IntPairList):
        '''
        充电站初始化。
            name: 充电站名称, 同时也对应SUMO中的道路名称.
            slots: 充电桩数量.
            node: 所属的配电网节点名称.
            price_buy: 用户充电电价列表, 元/kWh. 用法请看例子.
            price_sell: 用户售电电价列表, 元/kWh. 用法同price_buy. 设为None表示不支持V2G.
            offline: 充电站故障时间. 用法请看例子. 设为None表示永久在线.
        例子：
        ```py
        price_buy = ([  0, 28800, 36000],
                     [0.8,   1.5,   2.0])
        offline = [
            (0, 3600),
            (7200, 10800)
        ]
        ```
        例子的含义是：
        用户购电电价为
            0s (00:00:00) 到 28799s (07:59:59) 为0.8元/kWh,
            28800s (08:00:00) 到 35999s (09:59:59) 为1.5元/kWh,
            36000s (10:00:00) 到仿真结束 为2.0元/kWh。
        充电站故障时间为
            0s (00:00:00) 到 (3600-1)=3599s (00:59:59) 和
            7200s (02:00:00) 到 (10800-1)=3599s (02:59:59)。
        price和offiline中的时间必须是递增的.
        '''
        self._name:str=name
        self._slots:int=slots
        self._node:str=node
        self._pbuy:OverrideFunc=OverrideFunc(makeFunc(*price_buy))
        self._psell:Optional[OverrideFunc]=None if price_sell is None else OverrideFunc(makeFunc(*price_sell))
        self._offline:RangeList=RangeList(offline)
        self._cload:float = 0.0
        self._dload:float = 0.0
        self._cur_v2g_cap:float = 0.0
    
    def __str__(self):
        return f"CS[name='{self._name}',slots={self._slots},pbuy={self._pbuy},psell={self._psell},offline={self._offline}]"
    
    @property
    def name(self)->str: 
        '''充电站名称'''
        return self._name

    @property
    def slots(self)->int: 
        '''充电站的充电桩数量'''
        return self._slots

    @property
    def node(self)->str: 
        '''充电站所属配网节点'''
        return self._node

    @property
    def pbuy(self)->OverrideFunc: 
        '''车辆购电价格, 元/kWh'''
        return self._pbuy
    
    @property
    def psell(self)->OverrideFunc: 
        '''车辆售电价格, 元/kWh'''
        if self._psell is None: raise AttributeError("充电站%s不支持V2G"%self.name)
        return self._psell
    
    @property
    def supports_V2G(self)->bool:
        '''检查本充电站是否支持V2G'''
        return not self._psell is None
        
    def is_online(self,t:int)->bool:
        '''
        检查$t$秒时该充电站是否可用。
            t: 时间点
        返回：
            True如果可用
            False如果不可用(故障)
        '''
        return not t in self._offline
    
    @abstractmethod
    def add_veh(self,veh_id:str)->bool:
        '''
        添加车辆至充电队列。充电桩不足时等待。
            veh_id: 车辆ID
        返回:
            True如果添加成功，
            False如果该车已在充电。
        '''
        raise NotImplementedError
    
    @abstractmethod
    def pop_veh(self,veh_id:str)->bool:
        '''
        从充电队列中移除车辆
            veh_id: 车辆ID
        返回:
            True如果移除成功，
            False如果该车不存在。
        '''
        raise NotImplementedError
    
    @abstractmethod
    def __contains__(self,veh_id:str)->bool: pass

    def has_veh(self,veh_id:str)->bool:
        '''
        检车是否有指定ID的车辆.
            veh_id: 车辆ID
        返回:
            True如果存在,
            False如果不存在.
        '''
        return self.__contains__(veh_id)

    @abstractmethod
    def __len__(self)->int:
        '''返回充电站中的车辆数'''
        raise NotImplementedError

    @abstractmethod
    def update(self,ev_dict:EVDict,sec:int,cur_time:int,v2g_k:float)->list[str]:
        '''
        以当前参数, 对给定EVDict中的EV进行充放电, 充放电时长为sec秒.
            ev_dict: 本CS中存储的车辆ID对应的EVDict
            sec: 秒数
            cur_time: 当前时间
            v2g_k: V2G反向送电比例
        返回：
            移出CS的车辆列表
        '''
        raise NotImplementedError

    @abstractmethod
    def is_charging(self,veh_id:str)->bool:
        '''
        获取车辆充电状态. 如果车辆不存在则报错ValueError.
            veh_id: 车辆ID
        返回:
            True如果正在充电,
            False如果正在等待.
        '''
        raise NotImplementedError
    
    @abstractmethod
    def veh_count(self, only_charging=False)->int:
        '''
        返回充电站中的车辆数. 当only_charging为True时, 只返回正在充电的车辆数.
        '''
        raise NotImplementedError
    
    @abstractmethod
    def get_V2G_cap(self,ev_dict:EVDict)->float:
        '''
        根据给定EVDict中的EV进行充放电, 获取当前情况下V2G的最大功率, 单位kWh/s
        '''

    @property
    def Pc(self)->float: 
        '''当前充电功率, kWh/s'''
        return self._cload
    
    @property
    def Pc_kW(self)->float: 
        '''当前充电功率, kW, 3600kW = 1kWh/s'''
        return self._cload * 3600
    
    @property
    def Pc_MW(self)->float:
        '''当前充电功率, MW, 3.6MW = 1kWh/s'''
        return self._cload * 3.6
    
    @property
    def Pd(self)->float: 
        '''当前V2G放电功率, kWh/s'''
        return self._dload
    
    @property
    def Pd_kW(self)->float:
        '''当前V2G放电功率, kW, 3600kW = 1kWh/s'''
        return self._dload * 3600
    
    @property
    def Pd_MW(self)->float:
        '''当前V2G放电功率, MW, 3.6MW = 1kWh/s'''
        return self._dload * 3.6
    
    @property
    def Pv2g(self)->float:
        '''当前最大V2G放电功率, kWh/s'''
        return self._cur_v2g_cap

    @property
    def Pv2g_kW(self)->float:
        '''当前最大V2G放电功率, kW, 3600kW = 1kWh/s'''
        return self._cur_v2g_cap * 3600
    
    @property
    def Pv2g_MW(self)->float:
        '''当前最大V2G放电功率, MW, 3.6MW = 1kWh/s'''
        return self._cur_v2g_cap * 3.6
    
class SCS(CS):
    '''
    路边慢充 (Slow Charging Station)
    '''
    def __init__(self,name:str,slots:int,bus:str,price_buy:PriceList,price_sell:Optional[PriceList]=None,offline:IntPairList=[]):
        super().__init__(name,slots,bus,price_buy,price_sell,offline)
        self._chi:set[str] = set()       # 正在充电的车
        self._free:set[str] = set()      # 已经充满的车
    
    def add_veh(self,veh_id:str)->bool:
        if veh_id in self._chi or veh_id in self._free: return False
        if len(self._chi)+len(self._free)<self._slots:
            self._chi.add(veh_id)
            return True
        else: return False
    
    def pop_veh(self,veh_id:str)->bool:
        try:
            self._chi.remove(veh_id)
        except KeyError:
            try:
                self._free.remove(veh_id)
            except:
                return False
        return True
    
    def __contains__(self,veh_id:str)->bool:
        return veh_id in self._chi or veh_id in self._free

    def __len__(self)->int:
        return len(self._chi)+len(self._free)

    def is_charging(self,veh_id:str)->bool:
        return veh_id in self._chi
    
    def veh_count(self, only_charging=False)->int:
        if only_charging:
            return len(self._chi)
        else:
            return len(self._chi)+len(self._free)

    def get_V2G_cap(self,ev_dict:EVDict)->float:
        tot_rate_ava=0.0
        for veh_id in chain(self._chi,self._free):
            ev=ev_dict[veh_id]
            if ev.SOC>ev.kv2g: tot_rate_ava+=ev.max_v2g_rate*ev.eta_discharge
        self._cur_v2g_cap = tot_rate_ava
        return tot_rate_ava
    
    def update(self,ev_dict:EVDict,sec:int,cur_time:int,v2g_k:float)->list[str]:
        #assert 0<=v2g_k<=1
        Wcharge=0; Wdischarge=0; ret:list[str]=[]
        for veh_id in self._chi:
            ev=ev_dict[veh_id]
            # 如果正在进行V2G放电，就不要充满
            k=min(1,ev.kv2g) if v2g_k>0 else 1
            if ev.elec>=ev.charge_target*k:
                ret.append(veh_id)
            else:
                Wcharge+=ev.charge(sec, self.pbuy(cur_time))
        self._chi.difference_update(ret)
        self._free.update(ret)
        if v2g_k>0:
            for veh_id in self._free:
                ev=ev_dict[veh_id]
                if ev.SOC>ev.kv2g:
                    Wdischarge+=ev.discharge(v2g_k,sec,self.psell(cur_time))
        self._cload = Wcharge/sec
        self._dload = Wdischarge/sec
        return []
    
    
class FCS(CS):
    '''
    快充站 (Fast Charging Station)
    '''
    def __init__(self,name:str,slots:int,bus:str,price_buy:PriceList,price_sell:Optional[PriceList]=None,offline:IntPairList=[]):
        super().__init__(name,slots,bus,price_buy,price_sell,offline)
        self._max_slots:int=2*slots         # 最大充电桩数量
        self._def_slots:int=slots           # 默认充电桩数量
        self._min_slots:int=min(3,slots)    # 最小充电桩数量
        self._chi:set[str]=set()            # 正在充电的车
        self._buf:deque[str]=deque()        # 正在排队的车
        self._veh:set[str]=set()            # 所有站内车辆
    
    def add_veh(self,veh_id:str)->bool:
        if veh_id in self._veh: return False
        if len(self._chi)<self._slots: self._chi.add(veh_id)
        else: self._buf.append(veh_id)
        self._veh.add(veh_id)
        return True
    
    def pop_veh(self,veh_id:str)->bool:
        try:
            self._veh.remove(veh_id)
        except KeyError:
            return False
        try:
            self._chi.remove(veh_id)
        except:
            self._buf.remove(veh_id)
        return True

    def __contains__(self,veh_id:str)->bool:
        return veh_id in self._veh
    
    def is_charging(self,veh_id:str)->bool:
        return veh_id in self._chi
    
    def veh_count(self, only_charging=False)->int:
        if only_charging:
            return len(self._chi)
        else:
            return len(self._chi)+len(self._buf)
    
    def __len__(self)->int:
        return len(self._chi)+len(self._buf)
    
    def update(self,ev_dict:EVDict,sec:int,cur_time:int,v2g_k:float)->list[str]:
        # 快充站不考虑V2G
        Wcharge=0;ret=[]
        for veh_id in self._chi:
            ev=ev_dict[veh_id]
            Wcharge+=ev.charge(sec, self.pbuy(cur_time))
            if ev.battery>=ev.charge_target: ret.append(veh_id)
        for veh_id in ret:
            self.pop_veh(veh_id)
            if len(self._buf)>0: self._chi.add(self._buf.popleft())
        self._cload = Wcharge/sec
        return ret
    
    def get_V2G_cap(self,ev_dict:EVDict)->float: 
        # 快充站不考虑V2G
        return 0.0
    
    def change_slots(self, value:int):
        '''修改充电站的充电桩数量'''
        self._slots=max(min(value,self._max_slots),self._min_slots)
        # 如果充电桩数量增加，就把排队的车加入充电
        while len(self._chi)<self._slots and len(self._buf)>0:
            self._chi.add(self._buf.popleft()) 
        # 如果充电桩数量减少，不会对多出来的车做任何动作